﻿//-----------------------------------------------------------------------
// <copyright file="PivotTable.cs" company="Forebrain Technology, LLC">
//
// Copyright (c) Forebrain Technology, LLC. All rights reserved.
// 
// This source code is released under the Microsoft Public License (Ms-PL).
//
// </copyright>
//-----------------------------------------------------------------------
namespace PivotCollectionTools.Data
{
    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Linq;
    using System.Globalization;

    /// <summary>
    /// A table representation of a Pivot Collection.
    /// </summary>
    [Serializable]
    public class PivotTable
    {
        /// <summary>
        /// Storage for the rows of data.
        /// </summary>
        private List<PivotTableRow> dataRows;

        /// <summary>
        /// Use to store the columns for the instance.
        /// </summary>
        private List<PivotTableColumn> columns;

        /// <summary>
        /// Initializes a new instance of the <see cref="PivotTable"/> class.
        /// </summary>
        public PivotTable()
        {
            this.columns = new List<PivotTableColumn>();
            this.Columns = new PivotTableColumnCollection(this.columns);
            this.dataRows = new List<PivotTableRow>();
            this.DataRows = new ReadOnlyCollection<PivotTableRow>(this.dataRows);
            this.Categories = new PivotTableCategoryCollection();

            this.InitializeColumns();
        }

        /// <summary>
        /// Gets the columns.
        /// </summary>
        /// <value>The columns of the pivot table.</value>
        public PivotTableColumnCollection Columns { get; private set; }

        /// <summary>
        /// Gets the data rows.
        /// </summary>
        /// <value>The data rows of the pivot table.</value>
        public ReadOnlyCollection<PivotTableRow> DataRows { get; private set; }

        /// <summary>
        /// Gets the categories.
        /// </summary>
        /// <value>The categories of the pivot table.</value>
        public PivotTableCategoryCollection Categories { get; private set; }

        /// <summary>
        /// Gets the category numerOfColumns.
        /// </summary>
        /// <value>The category numerOfColumns of the pivot table.</value>
        public int CategoryCount
        {
            get
            {
                return this.Categories.Count;
            }
        }

        /// <summary>
        /// Creates the new row.
        /// </summary>
        /// <returns>The created column.</returns>
        public PivotTableRow NewRow()
        {
            PivotTableRow row = new PivotTableRow();
            foreach (var column in this.columns)
            {
                switch (column.Category.ValueType)
                {
                    case PivotTableCellType.Text:
                    default:
                        row.Cells.Add(new PivotTableCellStringType(column));
                        break;
                    case PivotTableCellType.Number:
                        row.Cells.Add(new PivotTableCellNumberType(column));
                        break;
                }
            }

            this.dataRows.Add(row);

            return row;
        }

        /// <summary>
        /// Clears this instance.
        /// </summary>
        public void Clear()
        {
            this.Categories.Clear();
            this.columns.Clear();
            this.dataRows.Clear();

            this.InitializeColumns();
        }

        /// <summary>
        /// Clears the data.
        /// </summary>
        public void ClearData()
        {
            this.dataRows.Clear();
        }

        /// <summary>
        /// Adds a column to the specified category.
        /// </summary>
        /// <param name="categoryName">Name of the category.</param>
        /// <returns>The addded column.</returns>
        public PivotTableColumn AddColumn(string categoryName)
        {
            if (categoryName == Resources.Resource.ItemAttributeCategoryCaption)
            {
                throw new ArgumentOutOfRangeException("categoryName", Resources.Resource.CannotAddColumnToAttributeColumn);
            }

            PivotTableCategory category = this.Categories.FirstOrDefault(c => c.Name == categoryName);
            if (category == null)
            {
                throw new ArgumentException(Resources.Resource.CategoryDoesNotExists, "categoryName");
            }

            string columnName = this.GetNextColumnNameForCategory(category);

            return this.AddColumn(columnName, category);
        }

        /// <summary>
        /// Adds the number of columns specified by the number of columns to the specified category.
        /// </summary>
        /// <param name="categoryName">Name of the category.</param>
        /// <param name="numberOfColumns">The numer of columns.</param>
        public void AddColumn(string categoryName, int numberOfColumns)
        {
            PivotTableCategory category = this.Categories.FirstOrDefault(c => c.Name == categoryName);
            if (category == null)
            {
                throw new ArgumentException("Category does not exist.", "categoryName");
            }

            string columnName;
            for (int counter = 0; counter < numberOfColumns; counter++)
            {
                columnName = this.GetNextColumnNameForCategory(category);
                this.AddColumn(columnName, category);
            }
        }
        
        /// <summary>
        /// Gets the next column name for category.
        /// </summary>
        /// <param name="category">The category.</param>
        /// <returns>A string with the name for the next column in the category.</returns>
        public string GetNextColumnNameForCategory(PivotTableCategory category)
        {
            bool gettigColumnName = true;
            string newColumn = category.Name;

            if (category.Columns.Count == 0)
            {
                return newColumn;
            }

            while (gettigColumnName)
            {
                if (newColumn.HasNumbers())
                {
                    int priorNumber = 0;
                    string rootName = string.Empty;
                    for (int counter = 0; counter < category.Name.Length; counter++)
                    {
                        int x = 0;
                        string test = newColumn.RightString(counter + 1);
                        if (test.Contains(" ") || !int.TryParse(test, out x))
                        {
                            break;
                        }

                        priorNumber = x;
                        rootName = newColumn.Substring(0, (newColumn.Length - 1) - counter);
                        rootName = rootName.Trim();
                    }

                    newColumn = string.Concat(rootName, " ", priorNumber + 1);
                }
                else
                {
                    newColumn = string.Concat(newColumn, " ", 1);
                }

                if (!category.Columns.Contains(newColumn))
                {
                    break;
                }
            }

            return newColumn;
        }

        /// <summary>
        /// Removes the column.
        /// </summary>
        /// <param name="columnName">Name of the column.</param>
        /// <exception cref="T:System.ArgumentOutOfRangeException">
        ///     <paramref name="columnName"/> is not found in the Pivot Table Column collection.
        /// -or-
        ///     <paramref name="columnName"/> belongs to the Item Attribute category which 
        ///     can not be deleted.
        /// </exception> 
        public void RemoveColumn(string columnName)
        {
            PivotTableColumn columnToRemove = this.columns.FirstOrDefault(c => c.Name == columnName);

            if (columnToRemove == null)
            {
                throw new ArgumentOutOfRangeException(columnName, Resources.Resource.ColumnDoesNotExists);
            }

            if (columnToRemove.Category.Name == Resources.Resource.ItemAttributeCategoryCaption)
            {
                throw new ArgumentOutOfRangeException(columnName, Resources.Resource.ColumnBelongsToItemAttribute);
            }

            PivotTableCategory categoryOfColumn = columnToRemove.Category;

            categoryOfColumn.RemoveColumn(columnToRemove);

            this.columns.RemoveAll(c => c.Name == columnName);

            if (this.DataRows.Count > 0)
            {
                foreach (var row in this.DataRows)
                {
                    var deleteCell = row.Cells.FirstOrDefault(c => c.Column.Name == columnName);
                    if (deleteCell == null)
                    {
                        return;
                    }

                    row.Cells.Remove(deleteCell);
                }
            }

            int counter = 0;
            foreach (var column in categoryOfColumn.Columns)
            {
                string newColumnName = categoryOfColumn.Name;
                
                if (counter == 0)
                {
                    column.Name = newColumnName;
                    continue;
                }

                newColumnName = string.Concat(categoryOfColumn.Name, " ", counter.ToString(CultureInfo.InvariantCulture));
                column.Name = newColumnName;
                
                counter++;
            }
        }

        /// <summary>
        /// Removes the row specified by the row index.
        /// </summary>
        /// <param name="rowIndex">Index of the row to remove.</param>
        public void RemoveRow(int rowIndex)
        {
            this.dataRows.RemoveAt(rowIndex);
        }

        /// <summary>
        /// Removes the columns by category.
        /// </summary>
        /// <param name="categoryName">Name of the category.</param>
        /// <exception cref="T:System.ArgumentOutOfRangeException">
        ///     <paramref name="categoryName"/> is not found in the Category collection.
        /// -or-
        ///     <paramref name="index"/> the cateogry is equal to Item Attributes which 
        ///     can not be deleted.
        /// </exception> 
        public void RemoveColumnsByCategory(string categoryName)
        {
            if (categoryName == "Item Attributes")
            {
                throw new ArgumentOutOfRangeException("categoryName", Resources.Resource.CategoryBelongsToItemAttribute);
            }

            var category = this.Categories.FirstOrDefault(c => c.Name == categoryName);

            if (category == null)
            {
                throw new ArgumentOutOfRangeException("categoryName", Resources.Resource.CategoryDoesNotExists);
            }
          
            this.columns.RemoveAll(c => c.Category.Name == categoryName);

            if (this.DataRows.Count > 0)
            {
                foreach (var row in this.DataRows)
                {
                    var deleteList = row.Cells.Where(c => c.Column.Category.Name == categoryName).ToList();
                    
                    foreach (var cell in deleteList)
                    {
                        row.Cells.Remove(cell);
                    }
                }
            }
        }

        /// <summary>
        /// Renames the specifed category name. Also renames the columns associated with the Category.
        /// </summary>
        /// <param name="pivotTableCategory">The pivot table category to rename.</param>
        /// <param name="newName">The new name of the category.</param>
        public void RenameCategory(PivotTableCategory pivotTableCategory, string newName)
        {
            pivotTableCategory.Name = newName;
            foreach (var column in pivotTableCategory.Columns)
            {
                column.Name = this.GetNextColumnNameForCategory(pivotTableCategory);
            }
        }

        /// <summary>
        /// Initializes the required columns for a pivot collection.
        /// </summary>
        private void InitializeColumns()
        {
            this.Categories.Add(new PivotTableCategory(Resources.Resource.ItemAttributeCategoryCaption));

            this.AddItemAttributeColumn(Resources.Resource.ItemImageLocationColumnCaption);
            this.AddItemAttributeColumn(Resources.Resource.ItemNameColumnCaption);
            this.AddItemAttributeColumn(Resources.Resource.ItemLinkColumnCaption);
            this.AddItemAttributeColumn(Resources.Resource.ItemDescriptionColumnCaption);
        }

        /// <summary>
        /// Adds the column.
        /// </summary>
        /// <param name="columnName">Name of the column.</param>
        /// <param name="category">The category.</param>
        /// <returns>The addded column.</returns>
        private PivotTableColumn AddColumn(string columnName, PivotTableCategory category)
        {
            PivotTableColumn newColumn;
            switch (category.ValueType)
            {
                case PivotTableCellType.Number:
                    newColumn = new PivotTableColumn(columnName, category);
                    break;
                default:
                    newColumn = new PivotTableColumn(columnName, category);
                    break;
            }

            this.columns.Add(newColumn);

            if (this.DataRows.Count > 0)
            {
                foreach (var row in this.DataRows)
                {
                    switch (category.ValueType)
                    {
                        case PivotTableCellType.Text:
                        default:
                            row.Cells.Add(new PivotTableCellStringType(newColumn));
                            break;
                        case PivotTableCellType.Number:
                            row.Cells.Add(new PivotTableCellNumberType(newColumn));
                            break;
                    }
                }
            }

            return newColumn;
        }

        /// <summary>
        /// Adds the column.
        /// </summary>
        /// <param name="columnName">Name of the column.</param>
        /// <returns>The addded column.</returns>
        private PivotTableColumn AddItemAttributeColumn(string columnName)
        {
            PivotTableCategory category = this.Categories.FirstOrDefault(c => c.Name == Resources.Resource.ItemAttributeCategoryCaption);

            return this.AddColumn(columnName, category);
        }
    }
}
